package encryption;

import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.signers.PlainDSAEncoding;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class SM2Key {
    /**
     * 指定私钥签名测试
     * <i scr="https://i.goto327.top/CryptTools/SM2.aspx?tdsourcetag=s_pctim_aiomsg">秘钥验证</i>
     */
    private final static BouncyCastleProvider bc = new BouncyCastleProvider();
    private final static String KEY_ALGORITHM = "EC";
    private final static String SIGNATURE_ALGORITHM = "SM3withSm2";
    /**
     * 使用openssl(1.1.1版本以上)生成sm2公私钥
     * openssl ecparam -genkey -name SM2 -noout -out pri.pem #生成私钥，私钥默认是pem文件格式
     * openssl pkcs8 -topk8 -inform PEM -in pri.pem -nocrypt -out pri_pkcs8.pem #将pem文件格式的私钥，转成java可解析的pkcs8格式
     * openssl ec -in pri_pkcs8.pem -pubout -out pub.pem #生成公钥
     */
    public static void main(String[] args) throws Exception {
        String text = "我是一段测试aaaa"; // 可以为‘’ 不能为null
        byte[] dataBytes = text.getBytes();
        // ------------------- 私钥解析 start-----------------------
        String priPath = "D:\\workspace\\dongfeng\\LT\\pri_pkcs8.pem";
        byte[] keyBytes = Base64.decodeBase64(read(priPath));
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM, bc);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
//        指定的私钥
//        String privateKeyHex = "1ebf8b341c695ee456fd1a41b82645724bc25d79935437d30e7e4b0a554baa5e";
//        ECPrivateKeyParameters privateKeyParameters = BCUtil.toSm2Params(privateKeyHex);

        // ------------------- 私钥解析 end  -----------------------
        // ------------------- 公钥解析 start-----------------------
        String pubPath = "D:\\workspace\\dongfeng\\LT\\pub.pem";
        byte[] pubkeyBytes = Base64.decodeBase64(read(pubPath));
        KeyFactory pubkeyFactory = KeyFactory.getInstance(KEY_ALGORITHM, bc);
        X509EncodedKeySpec pubkeySpec = new X509EncodedKeySpec(pubkeyBytes);
        PublicKey pubKey = pubkeyFactory.generatePublic(pubkeySpec);
//        String publicKeyHex = "04db9629dd33ba568e9507add5df6587a0998361a03d3321948b448c653c2c1b7056434884ab6f3d1c529501f166a336e86f045cea10dffe58aa82ea13d7253763";
//        //这里需要根据公钥的长度进行加工
//        if (publicKeyHex.length() == 130) {
//            //这里需要去掉开始第一个字节 第一个字节表示标记
//            publicKeyHex = publicKeyHex.substring(2);
//        }
//        String xhex = publicKeyHex.substring(0, 64);
//        String yhex = publicKeyHex.substring(64, 128);
//        ECPublicKeyParameters ecPublicKeyParameters = BCUtil.toSm2Params(xhex, yhex);
        // ------------------- 公钥解析 end  -----------------------
        // 生成签名
        SM2 sm2 = new SM2(priKey, pubKey); // 可以只有一个参数有效
        //这里需要手动设置，sm2 对象的默认值与我们期望的不一致 , 使用明文编码
        sm2.usePlainEncoding();
        sm2.setMode(SM2Engine.Mode.C1C2C3);
        byte[] sign = sm2.sign(dataBytes, null);
        System.out.println("数据: " + HexUtil.encodeHexStr(dataBytes));
        String signHex = HexUtil.encodeHexStr(sign);
        System.out.println("签名: " + HexUtil.encodeHexStr(sign));
        // 进行加密
        // 公钥加密
        String encryptStr = sm2.encryptBcd(text, KeyType.PublicKey);
        System.out.println("数据加密: " + encryptStr);
        // 解密
        // 私钥解密
        String decryptStr = StrUtil.utf8Str(sm2.decryptFromBcd(encryptStr, KeyType.PrivateKey));
        System.out.println("数据解密: " + decryptStr);
        //验证签名
        //这里需要手动设置，sm2 对象的默认值与我们期望的不一致 , 使用明文编码
        sm2.usePlainEncoding();
        sm2.setMode(SM2Engine.Mode.C1C2C3);
        boolean verify = sm2.verify(dataBytes, HexUtil.decodeHex(signHex));
        System.out.println("数据: " + HexUtil.encodeHexStr(dataBytes));
        System.out.println("验签结果: " + verify);
    }

    /**
     * 创建sm2测试
     * <i scr="https://i.goto327.top/CryptTools/SM2.aspx?tdsourcetag=s_pctim_aiomsg">秘钥验证</i>
     */
    public void createSm2KeyTest() {
        //需要加密的明文
        String text = "我是一段测试aaaa";
        //创建sm2 对象
        SM2 sm2 = SmUtil.sm2();
        //这里会自动生成对应的随机秘钥对 , 注意！ 这里一定要强转，才能得到对应有效的秘钥信息
        byte[] privateKey = BCUtil.encodeECPrivateKey(sm2.getPrivateKey());
        //这里公钥不压缩  公钥的第一个字节用于表示是否压缩  可以不要
        byte[] publicKey = ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false);
        //这里得到的 压缩后的公钥   ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(true);
        // byte[] publicKeyEc = BCUtil.encodeECPublicKey(sm2.getPublicKey());
        //打印当前的公私秘钥
        System.out.println("私钥: " + HexUtil.encodeHexStr(privateKey));
        System.out.println("公钥: " + HexUtil.encodeHexStr(publicKey));
        //得到明文对应的字节数组
        byte[] dateBytes = text.getBytes();
        System.out.println("数据: " + HexUtil.encodeHexStr(dateBytes));
        //这里需要手动设置，sm2 对象的默认值与我们期望的不一致
        sm2.setMode(SM2Engine.Mode.C1C2C3);
        sm2.setEncoding(new PlainDSAEncoding());
        //计算签名
        byte[] sign = sm2.sign(dateBytes, null);
        System.out.println("签名: " + HexUtil.encodeHexStr(sign));
        // 校验  验签
        boolean verify = sm2.verify(dateBytes, sign);
        System.out.println(verify);
    }

    public static String read(String filePath) throws IOException {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File(filePath)));
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            int size = 0;
            byte[] temp = new byte[128];
            while ((size = bis.read(temp)) > 0) {
                outputStream.write(temp, 0, size);
            }
            return new String(outputStream.toByteArray());
        }
    }

}
